home *** CD-ROM | disk | FTP | other *** search
/ The Atari Compendium / The Atari Compendium (Toad Computers) (1994).iso / files / prgtools / langs / iconv8_l.arc / PROGS.ARC / ipp.icn < prev    next >
Encoding:
Text File  |  1990-03-08  |  33.5 KB  |  1,064 lines

  1. ############################################################################
  2. #
  3. #    Name:    ipp.icn
  4. #
  5. #    Title:    Icon preprocessor
  6. #
  7. #    Author:    Robert C. Wieland
  8. #
  9. #    Date:    December 22, 1989
  10. #
  11. ############################################################################
  12. #
  13. #     Ipp is a preprocessor for the Icon language.  Ipp has many operations and
  14. #  features that are unique to the Icon environment and should not be used as a
  15. #  generic preprocessor (such as m4).  Ipp produces output which when written to
  16. #  a file is designed to be the source for icont, the command processor for Icon
  17. #  programs.
  18. #  
  19. #  Ipp may be invoked from the command line as:
  20. #
  21. #    ipp [option  ...] [ifile [ofile]]
  22. #  
  23. #     Two file names may be specified as arguments.  'ifile' and 'ofile' are 
  24. #  respectively the input and output files for the preprocessor.  By default
  25. #  these are standard input and standard output.  If the output file is to be
  26. #  specified while the input file should remain standard input a dash ('-')
  27. #  should be given as 'ifile'.  For example, 'ipp - test' makes test the output
  28. #  file while retaining standard input as the input file.
  29. #  
  30. #     The following special names are predefined by ipp and may not be redefined
  31. #  or undefined.  The name _LINE_ is defined as the line number (as an
  32. #  integer) of the line of the source file currently processed.  The
  33. #  name _FILE_ is defined as the name of the current source file (as a string).  
  34. #     If the source is standard input then it has the value 'stdin'.
  35. #  
  36. #     Also predefined are names corresponding to the features supported by the
  37. #  implementation of Icon at the location the preprocessor is run.  This allows
  38. #  conditional translations using the 'if' commands, depending on what features
  39. #  are available.  Given below is a list of the features on a 4.nbsd UNIX 
  40. #  implementation and the corresponding predefined names:
  41. #  
  42. #      Feature                Name
  43. #      -----------------------------------------------------
  44. #      UNIX                UNIX
  45. #      co-expressions            co_expressions
  46. #      overflow checking        overflow_checking
  47. #      direct execution        direct_execution
  48. #      environment variables        environment_variables
  49. #      error traceback            error_traceback
  50. #      executable images        executable_images
  51. #      string invocation        string_invocation
  52. #      expandable regions        expandable_regions
  53. #  
  54. #  
  55. #  Command-Line Options:
  56. #  ---------------------
  57. #  
  58. #    The following options to ipp are recognized:
  59. #  
  60. #   -C        By default ipp strips Icon-style comments.  If this option
  61. #         is specified all comments are passed along except those
  62. #         found on ipp command lines (lines starting with  a '$' 
  63. #         command).
  64. #   -D name    
  65. #   -D name=def    Allows the user to define a name on the command line instead
  66. #         of using a $define command in a source file.  In the first
  67. #         form the name is defined as '1'.  In the second form name is
  68. #         defined as the text following the equal sign.  This is less
  69. #         powerful than the $define command line since def can not
  70. #         contain any white space (spaces or tabs).
  71. #   -d depth    By default ipp allows include files to be nested to a depth
  72. #         of ten.  This allows the preprocessor to detect infinitely
  73. #         recursive include sequences.  If a different limit for the
  74. #         nesting depth is needed it may changed by using this option
  75. #         with an integer argument greater than zero. Also, if a file
  76. #         is found to already be in a nested include sequence an
  77. #         error message is written regardless of the limit.
  78. #   -I dir    The following algorithm is normally used in searching for
  79. #         $include files.  Names enclosed in <> are always expected to 
  80. #         in the /usr/icon/src directory.  On a UNIX system names enclosed
  81. #         in "" are searched for by trying in order the directories
  82. #         specified by the PATH environment variable.  On other systems
  83. #         only the current directory is searched.  If the -I option is
  84. #         given the directory specified is searched before the 'standard'
  85. #         directories.  If this option is specified more than once the
  86. #         directories specified are tried in the order that they appear
  87. #         on the command line, then followed by the 'standard' 
  88. #          directories.
  89. #  
  90. #  
  91. #  Preprocessor commands:
  92. #  ----------------------
  93. #  
  94. #     All ipp commands start with lines beginning with a '$'.  The name of the
  95. #  command must immediately follow the '$'.  Any line beginning with a '$'
  96. #  and not followed by a valid name will cause an error message to be sent
  97. #  to standard error and termination of the preprocessor.  If the command
  98. #  requires an argument then it must be separated from the command name by
  99. #  white space (any number of spaces or tabs) otherwise the argument will be
  100. #  considered part of the name and the result will likely produce an error.
  101. #  In processing the #  commands ipp responds to exceptional conditions in one
  102. #  of two ways.  It may produce a warning and continue processing or produce an
  103. #  error message and terminate.  In both cases the message is sent to standard
  104. #  error.  With the exception of error conditions encountered during the
  105. #  processing of the command line, the messages normally include the name and
  106. #  line number of the source file at the point the condition was
  107. #  encountered.  Ipp was designed so that most exception conditions
  108. #  encountered will produce errors and terminate.  This protects the user since
  109. #  warnings could simply be overlooked or misinterpreted.
  110. #
  111. #     Many ipp command require names as arguments.  Names must begin with a
  112. #  letter or an underscore, which may be followed by any number of letters,
  113. #  underscores, and digits.  Icon-style comments may appear on ipp command
  114. #  lines, however they must be separated from the normal end of the command by
  115. #  white_space.  If any extraneous characters appear on a command line a
  116. #  warning is issued.  This occurs when characters other than white-space or a
  117. #  comment follow the normal end of a command.
  118. #  
  119. #     The following commands are implemented:
  120. #  
  121. #    $define:  This command may be used in one of two forms.  The first form
  122. #           only allows simple textual substitution.  It would be invoked as
  123. #          '$define name text'.  Subsequent occurrencegs of name are replaced 
  124. #          with text.  Name and text must be separated by one white space
  125. #          character which is not considered to be part of the replacement
  126. #          text.  Normally the replacement text ends at the end of the line.
  127. #          The text however may be continued on the next line if the backslash
  128. #          character '\' is the last character on the line.  If name occurs
  129. #          in the replacement text an error message (recursive textual substi-
  130. #          tution) is written.
  131. #  
  132. #          The second form is '$define name(arg,...,arg) text' which defines
  133. #          a macro with arguments.  There may be no white space between the 
  134. #          name and the '('.  Each occurrenceg of arg in the replacement text
  135. #          is replaced by the formal arg specified when the macro is 
  136. #          encountered.   When a macro with arguments is expanded the arguments
  137. #          are placed into the expanded replacement text unchanged.  After the
  138. #          entire replacement text is expanded, ipp restarts its scan for names
  139. #          to expand at the beginning of the newly formed replacement text.  
  140. #          As with the first form above, the replacement text may be continued
  141. #          an following lines.  The replacement text starts immediately after
  142. #          the ')'. 
  143. #          The names of arguments must comply with the convention for regular 
  144. #          names.  See the section below on Macro processing for more 
  145. #          information on the replacement process.
  146. #  
  147. #    $undef:   Invoked as '$undef name'.   Removes the definition of name.  If
  148. #          name is not a valid name or if name is one of the reserved names
  149. #          _FILE_ or _LINE_ a message is issued.
  150. #  
  151. #    $include: Invoked as '$include <filename>' or '$include "filename"'.  This
  152. #          causes the preprocessor to make filename the new source until
  153. #          end of file is reached upon which input is again taken from the
  154. #          original source.  See the -I option above for more detail.
  155. #  
  156. #    $dump:    This command, which has no arguments, causes the preprocessor to 
  157. #          write to standard error all names which are currently defined.
  158. #          See '$ifdef' below for a definition of 'defined'.
  159. #  
  160. #    $endif:   This command has no arguments and ends the section of lines begun
  161. #          by a test command ($ifdef, $ifndef, or $if).  Each test command
  162. #          must have a matching $endif.
  163. #  
  164. #    $ifdef:   Invoked as 'ifdef name'.  The lines following this command appear
  165. #          in the output only if the name given is defined.  'Defined' means
  166. #            1.  The name is a predefined name and was not undefined using
  167. #            $undef, or
  168. #            2.  The name was defined using $define and has not been undefined
  169. #            by an intervening $undef.
  170. #  
  171. #    $ifndef:  Invoked as 'ifndef name'.  The lines following this command do not
  172. #          appear in the ouput if the name is not defined.
  173. #  
  174. #    $if:      Invoked as 'if constant-expression'.  Lines following this command
  175. #          are processed only if the constant-expression produces a result.
  176. #          The following arithmetic operators may be applied to integer 
  177. #          arguments: + - * / % ^
  178. #
  179. #          If an argument to one of the above operators is not an integer an
  180. #          error is produced.
  181. #  
  182. #             The following functions are provided: def(name), ndef(name)
  183. #          This allows the utility of $ifdef and $ifndef in a $if command.
  184. #          def produces a result if name is defined and ndef produces a
  185. #          result if name is not defined.  There must not be any white space
  186. #          between the name of the function and the '(' and also between the
  187. #          name and the surrounding parentheses.
  188. #          
  189. #             The following comparision operators may be used on integer
  190. #           operands:
  191. #
  192. #          > >= = < <= ~=
  193. #
  194. #              Also provided are alternation (|) and conjunction(&).  The
  195. #           following table lists all operators with regard to decreasing
  196. #           precedence:
  197. #  
  198. #          ^ (associates right to left)
  199. #          * / %
  200. #          + -
  201. #               > >= = < <= ~=
  202. #          |
  203. #          &
  204. #  
  205. #           The precedence of '|' and '&' are the same as the corresponding
  206. #           Icon counterparts.  Parentheses may be used for grouping.
  207. #  
  208. #    $else     This command has no arguments and reverses the notion of the test
  209. #          command which matches this directive.  If the lines preceding this
  210. #          command where ignored the lines following are processed, and vice
  211. #          versa.
  212. #  
  213. #  Macro Processing and Textual Substitution
  214. #  -----------------------------------------
  215. #     No substitution is performed on text inside single quotes (cset literals)
  216. #  and double quotes (strings) when a line is processed.   The preprocessor will
  217. #  detect unclosed cset literals or strings on a line and issue an error message
  218. #  unless the underscore character is the last character on the line.  The
  219. #  output from 
  220. #  
  221. #      $define foo bar
  222. #      write("foo")
  223. #  
  224. #  is
  225. #
  226. #       write("foo")
  227. #  
  228. #     Unless the -C option is specified comments are stripped from the source.
  229. #  Even if the option is given the text after the '#' is never expanded.
  230. #  
  231. #     Macro formal parameters are recognized in $define bodies even inside cset 
  232. #  constants and strings.  The output from
  233. #  
  234. #      $define test(a)        "a"
  235. #      test(processed)
  236. #  
  237. #  is the following sequence of characters: "processed".
  238. #  
  239. #     Macros are not expanded while processing a $define or $undef.  Thus:
  240. #  
  241. #      $define off invalid
  242. #      $define bar off
  243. #      $undef off
  244. #      bar
  245. #  
  246. #  produces off.  The name argument to $ifdef or $ifndef is also not expanded.
  247. #  
  248. #     Mismatches between the number of formal and actual parameters in a macro
  249. #  call are caught by ipp.  If the number of actual parameters is greater than
  250. #  the number of formal parameters is error is produced.  If the number of
  251. #  actual parameters is less than the number of formal parameters a warning is
  252. #  issued and the missing actual parameters are turned into null strings.
  253. #  
  254. ############################################################################
  255. #
  256. #    The records and global variables used by ipp are described below:
  257. #
  258. #  Src_desc:        Record which holds the 'file descriptor' and name
  259. #            of the corresponding file.  Used in a stack to keep
  260. #                track of the source files when $includes are used.
  261. #  Opt_rec         Record returned by the get_args() routine which returns
  262. #            the options and arguments on the command line.  options
  263. #            is a cset containing options that have no arguments.
  264. #            pairs is a list of [option,  argument] pairs. ifile and
  265. #            ofile are set if the input or output files have been
  266. #            specified.
  267. #  Defs_rec        Record stored in a table keyed by names.  Holds the
  268. #            names of formal arguments, if any, and the replacement
  269. #            text for that name.
  270. #  Chars        Cset of all characters that may appear in the input.
  271. #  Defs            The table holding the definition data for each name.
  272. #  Depth        The maximum depth of the input source stack.
  273. #  Ifile        Descriptor for the input file.
  274. #  Ifile_name        Name of the input file.
  275. #  Init_name_char     Cset of valid initial characters for names.
  276. #  Line_no        The current line number.
  277. #  Name_char        Cset of valid characters for names.
  278. #  Non_name_char    The complement of the above cset.
  279. #  Ofile        The descriptor of the output file.
  280. #  Options        Cset of no-argument options specified on the command
  281. #            line.
  282. #  Path_list        List of directories to search in for "" include files.
  283. #  Src_stack        The stack of input source records.
  284. #  Std_include_paths    List of directories to search in for <> include files.
  285. #  White_space        Cset for white-space characters.
  286. #  TRUE            Defined as 1.
  287. #
  288. ############################################################################
  289.  
  290. record Src_desc(fd, fname)
  291. record Opt_rec(options, pairs, ifile, ofile)
  292. record Defs_rec(arg_list, text)
  293.  
  294. global Chars, Defs, Depth, Ifile, Ifile_name, Init_name_char, 
  295.   Line_no, Name_char, Non_name_char, Ofile, Options, Path_list, 
  296.   Src_stack, Std_include_paths, White_space, TRUE 
  297.  
  298. procedure main(arg_list)
  299.   local cmd, line, source
  300.  
  301.   init(arg_list)
  302.  
  303.   repeat {
  304.     while line := read(Ifile) do {
  305.       Line_no +:= 1
  306.       line ? { 
  307.     if tab(any('$')) then
  308.       if cmd := tab(many(Chars)) then
  309.         process_cmd(cmd)
  310.       else
  311.         error("Missing command")
  312.     else
  313.       write(Ofile, process_text(line))
  314.         }
  315.       }
  316.     # Get new source
  317.     close(Ifile)
  318.     if source := pop(Src_stack) then {
  319.       Ifile := source.fd
  320.       Ifile_name := source.fname
  321.       Line_no := 0
  322.       }
  323.     else  break
  324.   }
  325. end
  326.  
  327. procedure process_cmd(cmd)
  328.   case cmd of {
  329.     "dump":        dump()
  330.     "define":        define()
  331.     "undef":        undefine()
  332.     "include":        include()
  333.     "if":        if_cond()
  334.     "ifdef":        ifdef()
  335.     "ifndef":        ifndef()
  336.     "else" | "endif":    error("No previous 'if' expression")    
  337.     "endif":        error("No previous 'if' expression")    
  338.     default:        error("Undefined command")
  339.     }
  340.   return
  341. end
  342.  
  343. procedure init(arg_list)
  344.   local s
  345.  
  346.   TRUE := 1
  347.   Defs := table()
  348.   Init_name_char := &letters ++ '_'
  349.   Name_char := Init_name_char ++ &digits
  350.   Non_name_char := ~Name_char
  351.   White_space := ' \t\b'
  352.   Chars := &ascii -- White_space
  353.   Line_no := 0
  354.   Depth := 10
  355.   Std_include_paths := ["/usr/icon/src"]
  356.  
  357.   # Predefine features
  358.   every s:= &features do {
  359.     s[upto('  -', s)] := "_"
  360.     Defs[s] := Defs_rec([], "1")
  361.     }
  362.  
  363.   # Set path list for $include files given in ""
  364.   Path_list := []
  365.   if \Defs["UNIX"] then 
  366.     getenv("PATH") ? while put(Path_list, 1(tab(upto(':')), move(1)))
  367.   else
  368.     put(Path_list, "")
  369.  
  370.   process_options(arg_list)
  371. end
  372.  
  373. procedure process_options(arg_list)
  374.   local args, arg_opts, pair, simple_opts, tmp_list, value
  375.  
  376.   simple_opts := 'C'
  377.   arg_opts := 'dDI'
  378.   Src_stack := []
  379.  
  380.   args := get_args(arg_list, simple_opts, arg_opts)
  381.   if \args.ifile then {
  382.     (Ifile := open(args.ifile)) | stop("Can not open input file ", args.ifile)
  383.     Ifile_name := args.ifile
  384.     }
  385.   else {
  386.     Ifile := &input
  387.     Ifile_name := "stdin"
  388.     }
  389.   if \args.ofile then 
  390.     (Ofile := open(args.ofile, "w")) | stop("Can not open output file",
  391.       args.ofile)
  392.   else 
  393.     Ofile := &output
  394.  
  395.   Options := args.options 
  396.   tmp_list := []
  397.   every pair := !args.pairs do
  398.     case pair[1] of {
  399.       "D":    def_opt(pair[2])
  400.       "d":    if (value := integer(pair[2])) > 0 then
  401.           Depth := value
  402.         else
  403.           stop("Invalid argument for depth")
  404.       "I":    push(tmp_list, pair[2])
  405.     }
  406.   Path_list := tmp_list ||| Path_list
  407. end
  408.  
  409. procedure get_args(arg_list, simple_opts, arg_opts)
  410.   local arg, ch, get_ofile, i, opts, queue
  411.   opts := Opt_rec('', [])
  412.   queue := []
  413.  
  414.   every arg := arg_list[i := 1 to *arg_list] do
  415.     if arg == "-" then         # Next argument should be output file
  416.       get_ofile := (i = *arg_list - 1) | 
  417.     stop("Invalid position of '-' argument")
  418.     else if arg[1] == "-" then     # Get options
  419.       every ch := !arg[2: 0] do
  420.     if any(simple_opts, ch) then
  421.       opts.options ++:= ch
  422.     else if any(arg_opts, ch) then
  423.       put(queue, ch)
  424.     else
  425.       stop("Invalid option - ", ch)
  426.     else if ch := pop(queue) then     # Get argument for option
  427.       push(opts.pairs, [ch, arg])
  428.     else if \get_ofile then {     # Get output file
  429.       opts.ofile := arg
  430.       get_ofile := &null
  431.       }
  432.     else {            # Get input file
  433.       opts.ifile := arg
  434.       get_ofile := (i < *arg_list)
  435.       }
  436.  
  437.   if \get_ofile | *queue ~= 0 then
  438.     stop("Invalid number of arguments")
  439.  
  440.   return opts
  441. end
  442.  
  443. # if_cond is the procedure for $if.  The procedure const_expr() which 
  444. # evaluates the constant expression may be found in expr.icn
  445. #
  446. # Procedure true_cond is invoked if the evaluation of a previous $if, $ifdef, or
  447. # $ifndef causes subsequent lines to be processed.  Lines will be processed
  448. # upto a $endif or a $else.  If $else is encountered, lines are skipped until
  449. # the $endif matching the $else is encountered.
  450. #
  451. # Procedure false_cond is invoked if the evaluation of a previous $if, $ifdef, 
  452. # or $ifndef causes subsequent lines to be skipped.  Lines will be skipped 
  453. # upto a $endif or a $else.  If $else is encountered, lines are processed until
  454. # the $endif matching the $else is encountered.
  455. #
  456. # If called with a 1, procedure skip_to skips over lines until a $endif is 
  457. # encountered.  If called with 2, it skips until either a $endif or $else is 
  458. # encountered.
  459.  
  460. procedure if_cond()
  461.   local expr 
  462.  
  463.   if expr := (tab(many(White_space)) & not pos(0) & tab(0)) then 
  464.     conditional(const_expr(expr))
  465.   else
  466.     error("Constant expression argument to 'if' missing")
  467. end
  468.  
  469. procedure ifdef()
  470.   local name
  471.  
  472.   if name := 2(tab(many(White_space)), tab(any(Init_name_char)) ||
  473.     (tab(many(Name_char)) | ""), any(White_space) | pos(0)) then  {
  474.     tab(many(White_space))
  475.     if not(pos(0) | any('#')) then
  476.       warning("Extraneous characters after argument to 'ifdef'")
  477.     conditional(Defs[name])
  478.     }
  479.   else
  480.     error("Argument to 'ifdef' is not a valid name")
  481. end
  482.   
  483. procedure ifndef()
  484.   local name
  485.  
  486.   if name := 2(tab(many(White_space)), tab(any(Init_name_char)) ||
  487.     (tab(many(Name_char)) | ""), any(White_space) | pos(0)) then {
  488.     tab(many(White_space))
  489.     if not(pos(0) | any('#')) then
  490.       warning("Extraneous characters after argument to 'ifndef'")
  491.     if \Defs[name] then
  492.       conditional(&null)
  493.     else
  494.       conditional(TRUE)
  495.     }
  496.   else
  497.     error("Argument to 'ifndef' is not a valid name")
  498. end
  499.   
  500. procedure conditional(flag)
  501.  
  502.   if \flag then
  503.     true_cond()
  504.   else
  505.     false_cond()
  506. end
  507.  
  508. procedure true_cond()
  509.   local line
  510.  
  511.   while line := read(Ifile) & (Line_no +:= 1) do
  512.     line ? {
  513.       if tab(any('$')) then
  514.         if tab(match("if")) then
  515.           eval_cond()
  516.         else if check_cmd("else") then {
  517.       # Skip only until a $endif
  518.       skip_to(1) |
  519.             error("'endif' not encountered before end of file")
  520.       return
  521.       }
  522.         else if check_cmd("endif") then
  523.       return
  524.     else 
  525.       process_cmd(tab(many(Chars))) | error("Undefined command")
  526.       else
  527.         write(Ofile, process_text(line))
  528.       }
  529.      
  530.     error("'endif' not encountered before end of file")
  531. end
  532.  
  533. procedure false_cond()
  534.   local cmd, line
  535.  
  536.   # Skip to $else or $endif
  537.   (cmd := skip_to(2)) | error("'endif' not encountered before end of file")
  538.   if cmd == "endif" then
  539.     return
  540.  
  541.   while line := read(Ifile) & (Line_no +:= 1) do
  542.     line ? {
  543.       if tab(any('$')) then
  544.     if check_cmd("endif") then
  545.       return
  546.     else if tab(match("if")) then
  547.       eval_cond()
  548.     else 
  549.       process_cmd(tab(many(Chars))) | error("Undefined command")
  550.       else
  551.         write(Ofile, process_text(line))
  552.       }
  553.   error("'endif' not encountered before end of file")
  554. end
  555.  
  556. procedure eval_cond()
  557.     if tab(match("def")) & (any(White_space) | pos(0)) then
  558.       ifdef()
  559.     else if tab(match("ndef")) & (any(White_space) | pos(0)) then 
  560.       ifndef()
  561.     else if any(White_space) | pos(0) then
  562.       return const_expr(tab(0))
  563.     else
  564.       error("Undefined command")
  565. end
  566.  
  567. procedure check_cmd(cmd)
  568.   local s
  569.  
  570.   if (s := tab(match(cmd))) & (tab(many(White_space)) | pos(0)) then {
  571.     if not(match("if", cmd) | pos(0) | any('#')) then
  572.       warning("Extraneous characters after command")
  573.     return s
  574.     }
  575.   else
  576.     fail
  577. end
  578.  
  579. procedure skip_to(n)
  580.   local cmd, ifs, elses, line, s
  581.  
  582.   ifs := elses := 0
  583.   while line := read(Ifile) & (Line_no +:= 1) do
  584.     line ? {
  585.       if tab(any('$')) then
  586.     if cmd := (check_cmd("endif") | (n = 2 & check_cmd("else"))) then
  587.       if ifs = elses = 0 then
  588.         return cmd
  589.       else if cmd == "endif" then {
  590.         ifs -:= 1
  591.         elses := 0
  592.         }
  593.       else if elses = 0 then
  594.         if ifs > 0 then
  595.           elses := 1
  596.         else
  597.           error("'$else' encountered before 'if'")
  598.       else
  599.         error("Previous '$else' not terminated by 'endif'")
  600.     else if check_cmd("endif") then {
  601.       ifs -:= 1
  602.       elses := 0
  603.       }
  604.         else if check_cmd("if" | "ifdef" | "ifndef") then
  605.           ifs +:= 1
  606.         else         # $else
  607.           if elses = 0 then
  608.             if ifs > 0 then
  609.               elses := 1
  610.             else
  611.               error("'$else' encountered before 'if'")
  612.        else 
  613.          error("Previous '$else' not terminated by 'endif'")
  614.    }
  615. end
  616.  
  617. procedure define()
  618.   local args, name, text
  619.  
  620.   if name := 2(tab(many(White_space)), tab(any(Init_name_char)) ||
  621.     (tab(many(Name_char)) | ""), any(White_space | '(') | pos(0)) then {
  622.     if name == ("_LINE_" | "_FILE_") then
  623.       error(name, " is a reserved name and can not be redefined")
  624.  
  625.     if tab(any('(')) then {         # A macro
  626.       if not upto(')') then
  627.     error("Missing ')' in macro definition")
  628.       args := get_formals()
  629.       text := get_text(TRUE)
  630.       }
  631.     else {
  632.       args := []
  633.       text := get_text()
  634.       }
  635.  
  636.     if \Defs[name] then
  637.       warning(name, " redefined")
  638.     Defs[name] := Defs_rec(args, text)
  639.     }  
  640.   else
  641.     error("Illegal or missing name in define")
  642. end
  643.  
  644. procedure get_text(flag)
  645.   local get_cont, text, line
  646.  
  647.   if \flag then
  648.     text := (tab(many(White_space)) | "") || tab(0)
  649.   else
  650.     text := (tab(any(White_space)) & tab(0)) | ""
  651.   if text[-1] == "\\" then {
  652.     get_cont := TRUE
  653.     text[-1] := ""
  654.     while line := read(Ifile) do {
  655.     Line_no +:= 1
  656.       text ||:= line
  657.       if text[-1] == "\\" then
  658.         text[-1] := ""
  659.       else {
  660.         get_cont := &null
  661.         break
  662.         }
  663.       }
  664.     }
  665.   if \get_cont then
  666.     error("Continuation line not found before end of file")
  667.   return text
  668. end
  669.  
  670. procedure get_formals()
  671.   local arg, args, ch, edited
  672.  
  673.   args := []
  674.   while arg := 1(tab(upto(',)')), ch := move(1)) do {
  675.     if edited := (arg ? 2(tab(many(White_space)) | TRUE, 
  676.       tab(any(Init_name_char)) || (tab(many(Name_char)) | ""),
  677.       tab(many(White_space)) | pos(0))) then
  678.         put(args, edited)
  679.     else if arg == "" then
  680.       return [""] 
  681.     else
  682.       error("Invalid formal argument in macro definition")
  683.     if ch == ")" then 
  684.       break
  685.     }
  686.   return args
  687. end
  688.  
  689. procedure undefine()
  690.   local name
  691.  
  692.   if name := (tab(many(White_space)) & tab(many(Chars))) then {
  693.     tab(many(White_space))
  694.     if not(pos(0) | any('#')) then
  695.       warning("Extraneous characters after argument to undef")
  696.     if not(name ? (tab(any(Init_name_char)), (tab(many(Name_char)) | ""), 
  697.       pos(0))) then
  698.       warning("Argument to undef is not a valid name")
  699.     if name == ("_LINE_" | "_FILE_") then
  700.       error(name, " is a reserved name that can not be undefined")
  701.     \Defs[name] := &null
  702.     }
  703.   else
  704.     error("Name missing in undefine")
  705. end
  706.  
  707. procedure process_text(line)
  708.   local add, entry, new, position, s, token
  709.   static in_string, in_cset
  710.  
  711.   new :=  ""
  712.   while *line > 0 do {
  713.     add := ""
  714.     line ? {
  715.       if \in_string then {
  716.     if new ||:= (tab(upto('"')) || move(1)) then
  717.       in_string := &null
  718.     else {
  719.       new ||:= tab(0)
  720.       if line[-1] ~== "_" then {
  721.         in_string := &null
  722.         warning("Unclosed double quote")
  723.         }
  724.       }
  725.         }        
  726.       if \in_cset then {
  727.     if new ||:= (tab(upto('\'')) || move(1)) then
  728.       in_cset := &null
  729.     else {
  730.       new ||:= tab(0)
  731.       if line[-1] ~== "_" then {
  732.         in_cset := &null
  733.         warning("Unclosed single quote")
  734.         }
  735.       }
  736.     }   
  737.  
  738.       new ||:= tab(many(White_space))
  739.       if token := tab(many(Name_char) | any(Non_name_char)) then {
  740.     if token == "\"" then { # Process string
  741.       new ||:= "\""
  742.           if \in_string then 
  743.         in_string := &null
  744.       else {
  745.         in_string := TRUE 
  746.         if pos(0) then {
  747.           warning("Unclosed double quote")
  748.           in_string := &null
  749.           }
  750.         }
  751.       add ||:= tab(0)
  752.       }
  753.     else if token == "'" then { # Process cset literal
  754.       new ||:= "'"
  755.           if \in_cset then 
  756.         in_cset := &null
  757.       else {
  758.         in_cset := TRUE 
  759.         if pos(0) then {
  760.           warning("Unclosed single quote")
  761.           in_cset := &null
  762.           }
  763.         }
  764.       add ||:= tab(0)
  765.       }
  766.     else if token == "#" then {
  767.           if any(Options, "C") then
  768.             new ||:= token || tab(0) 
  769.           else
  770.         (new ||:= (token ? tab(upto('#')))) & tab(0)
  771.       }
  772.     else if token == "_LINE_" then
  773.       new ||:= string(Line_no)
  774.     else if token == "_FILE_" then
  775.       new ||:= Ifile_name
  776.         else if /(entry := Defs[token]) then
  777.         new ||:= token
  778.     else if *entry.arg_list = 0 then
  779.       if in_text(token, entry.text) then
  780.         error("Recursive textual substitution")
  781.       else
  782.         add := entry.text
  783.     else if *entry.arg_list = 1 & entry.arg_list[1] == "" then {
  784.        if move(2) == "()" then
  785.          add := entry.text
  786.        else
  787.              error(token, ":  Invalid macro call")
  788.        }
  789.         else {  # Macro with arguments
  790.       s := tab(bal(White_space, '(', ')') | 0)
  791.       if not any('(', s) then
  792.             error(token, ":  Incomplete macro call")
  793.           add := process_macro(token, entry, s)
  794.       }
  795.         }
  796.       position := &pos
  797.       }
  798.     line := add || line[position: 0]
  799.     }
  800.   return new
  801. end
  802.  
  803. procedure process_macro(name, entry, s)
  804.   local arg, args, new_entry, news, token
  805.  
  806.   s ? {
  807.     args := []
  808.     if tab(any('(')) then {
  809.       repeat {
  810.     arg := tab(many(White_space)) | ""
  811.         if token := tab(many(Chars -- '(,)')) then {
  812.           if /(new_entry := Defs[token]) then
  813.           arg ||:= token
  814.       else if *new_entry.arg_list = 0 then
  815.         arg ||:= new_entry.text
  816.           else {  # Macro with arguments
  817.         if news := tab(bal(' \t\b,)', '(', ')')) then
  818.               arg ||:= process_macro(token, new_entry, news)
  819.         else
  820.               error(token, ":  Error in arguments to macro call")
  821.         }
  822.       } # if
  823.     else if not any(',)') then
  824.           error(name, ":  Incomplete macro call")
  825.     arg ||:= tab(many(White_space))
  826.         put(args, arg)
  827.     if any(')') then
  828.       break
  829.     move(1)
  830.         } # repeat 
  831.         if *args > *entry.arg_list then
  832.           error(name, ":  Too many arguments in macro call")
  833.     else if *args < *entry.arg_list then
  834.           warning(name, ":  Missing arguments in macro call")
  835.         return macro_call(entry, args)
  836.       } # if
  837.     }
  838. end
  839.  
  840. procedure macro_call(entry, args)
  841.   local i, map, result, token, x, y
  842.  
  843.   x := create !entry.arg_list
  844.   y := create !args
  845.   map := table()
  846.   while map[@x] := @y | ""
  847.  
  848.   entry.text ? {
  849.     result := tab(many(Non_name_char)) | ""
  850.     while token := tab(many(Name_char)) do {
  851.       result ||:= \map[token] | token
  852.       result ||:= tab(many(Non_name_char))
  853.       }
  854.     }
  855.   return result
  856. end
  857.  
  858. procedure in_text(name, text)
  859.   text ? 
  860.     return (pos(1) & tab(match(name)) & (upto(Non_name_char) | pos(0))) |
  861.       (tab(find(name)) & move(-1) & tab(any(Non_name_char)) & move(*name) &
  862.     any(Non_name_char) | pos(0))
  863. end
  864.  
  865. # In order to simplify the evaluation the three relational operators that
  866. # are longer than one character (<= ~= >=) are replaced by one character
  867. # 'aliases'.
  868. #
  869. # One problem with eval_expr() is that the idea of failure as opposed to
  870. # returning some special value can not be used.  For example if def(UNIX)
  871. # fails eval_expr() would try to convert it to an integer as its next step.
  872. # We would only want func() to fail if the argument is not a valid function,
  873. # not if the function is valid and the call fails.  'Failure' is therefore
  874. # represented by &null.
  875.  
  876. procedure const_expr(expr)
  877.   local new, temp
  878.  
  879.   new := ""
  880.   every new ||:= (" " ~== !expr)
  881.   while new[find(">=", new) +: 2] := "\200" 
  882.   while new[find("<=", new) +: 2] := "\201" 
  883.   while new[find("~=", new) +: 2] := "\202" 
  884.   return \eval_expr(new) | &null
  885.  
  886. end
  887.  
  888. procedure eval_expr(expr)
  889.   while expr ?:= 2(="(", tab(bal(')')), pos(-1))
  890.   return lassoc(expr, '&') | lassoc(expr, '|') | 
  891.     lassoc(expr, '<=>\200\201\202' | '+-' | '*/%') | rassoc(expr, '^') | 
  892.     func(expr) | integer(process_text(expr)) | error(expr, " :  Integer expected")
  893. end
  894.  
  895. procedure lassoc(expr, op)
  896.   local j
  897.  
  898.   expr ? {
  899.     every j := bal(op)
  900.     return eval(tab(\j), move(1), tab(0))
  901.     }
  902. end
  903.  
  904. procedure rassoc(expr, op)
  905.   return expr ? eval(tab(bal(op)), move(1), tab(0))
  906. end
  907.  
  908. procedure func(expr)
  909.   local name, arg
  910.  
  911.   expr ? {
  912.     (name := tab(upto('(')),
  913.     arg := (move(1) & tab(upto(')')))) | fail 
  914.     }
  915.   if \name == ("def" | "ndef") then
  916.     return name(arg)
  917.   else
  918.     error("Invalid function name") 
  919. end
  920.  
  921. procedure eval(arg1, op, arg2)
  922.   arg1 := process_text(\eval_expr(arg1)) | &null
  923.   arg2 := process_text(\eval_expr(arg2)) | &null
  924.   if (op ~== "&") & (op ~== "|") then
  925.     (integer(arg1) & integer(arg2)) |
  926.       error(map(op), " :  Arguments must be integers")
  927.   return case op of {
  928.     "+":    arg1 + arg2
  929.     "-":    arg1 - arg2
  930.     "*":    arg1 * arg2
  931.     "/":    arg1 / arg2
  932.     "%":    arg1 % arg2
  933.     "^":    arg1 ^ arg2
  934.     ">":     arg1 > arg2
  935.     "=":    arg1 = arg2
  936.     "<":    arg1 < arg2
  937.     "\200":    arg1 >= arg2
  938.     "\201":    arg1 <= arg2    
  939.     "\202":    arg1 ~= arg2
  940.     "|":    alt(arg1, arg2)    
  941.     "&":    conjunction(arg1, arg2)
  942.     }
  943. end
  944.  
  945. procedure def(name)
  946.   if \Defs[name] then
  947.     return ""
  948.   else
  949.     return &null
  950. end
  951.  
  952. procedure ndef(name)
  953.   if \Defs[name] then
  954.     return &null
  955.   else
  956.     return "" 
  957. end
  958.  
  959. procedure alt(x, y)
  960.   if \x then
  961.     return x
  962.   else if \y then
  963.     return y
  964.   else
  965.     return &null
  966. end
  967.  
  968. procedure conjunction(x, y)
  969.   if \x & \y then
  970.     return y
  971.   else
  972.     return &null
  973. end
  974.  
  975. procedure map(op)
  976.   return case op of {
  977.     "\200":     ">="
  978.     "\201":     "<="
  979.     "\202":     "~="
  980.     default:     op
  981.     }
  982. end
  983.  
  984. procedure dump()
  985.   tab(many(White_space))
  986.   if not(pos(0) | any('#')) then
  987.     warning("Extraneous characters after dump command")
  988.   every write(&errout, (!sort(Defs))[1])
  989. end
  990.  
  991. procedure include()
  992.   local ch, fname 
  993.   static fname_chars
  994.  
  995.   initial fname_chars := Chars -- '<>"'
  996.  
  997.   if fname := 3(tab(many(White_space)), (tab(any('"')) & (ch := "\"")) |
  998.     (tab(any('<')) & (ch := ">")), tab(many(fname_chars)), 
  999.     tab(any('>"')) == ch, tab(many(White_space)) | pos(0)) then {
  1000.     if not(pos(0) | any('#')) then
  1001.       warning("Extraneous characters after include file name")
  1002.     if ch == ">" then 
  1003.       find_file(fname, Std_include_paths)
  1004.     else
  1005.       find_file(fname, Path_list)
  1006.     }
  1007.   else
  1008.     error("Missing or invalid include file name")
  1009. end
  1010.     
  1011. procedure find_file(fname, path_list)
  1012.   local ifile, ifname, path 
  1013.  
  1014.   every path := !path_list do {
  1015.     if path == ("" | ".") then
  1016.       ifname := fname
  1017.     else
  1018.       ifname := path || "/" || fname
  1019.     if ifile := open(ifname) then {
  1020.       if *Src_stack >= Depth then {
  1021.     close(ifile)
  1022.         error("Possibly infinitely recursive file inclusion")
  1023.     }
  1024.       if ifname == (Ifile_name | (!Src_stack).fname) then
  1025.         error("Infinitely recursive file inclusion")
  1026.       push(Src_stack, Src_desc(Ifile, Ifile_name))
  1027.       Ifile := ifile
  1028.       Ifile_name := ifname
  1029.       Line_no := 0
  1030.       return
  1031.       }
  1032.     }
  1033.     error("Can not open include file ", fname)
  1034. end
  1035.  
  1036. procedure def_opt(s)
  1037.   local name, text, Name
  1038.  
  1039.   s ? {
  1040.     name := tab(upto('=')) | tab(0)
  1041.     text := (move(1) & tab(0)) | "1"
  1042.     }
  1043.   if name == ("_LINE_" | "_FILE_") then
  1044.     error(name, " is a reserved name and can not be redefined by the -D option")
  1045.   if name ~==:= (tab(any(Init_name_char)) & tab(many(Name_char)) & pos(0)) then
  1046.     error(name, " :  Illegal name argument to -D option")
  1047.   if \Defs[Name] then
  1048.     warning(name, " : redefined by -D option")
  1049.   Defs[name] := Defs_rec([], text)
  1050. end
  1051.  
  1052. procedure warning(s1, s2)
  1053.   s1 ||:= \s2
  1054.   write(&errout, Ifile_name, ":  ", Line_no, ":  ", "Warning  " || s1)
  1055. end
  1056.  
  1057. procedure error(s1, s2)
  1058.   s1 ||:= \s2
  1059.   stop(Ifile_name, ":  ", Line_no, ":  ", "Error  " || s1)
  1060. end
  1061.